package org.slusk.thynwor.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.jgap.Chromosome;
import org.slusk.thynwor.layout.Layout;
import org.slusk.thynwor.onlinereinforcement.ActivatorNDMultiBackProp;

import com.anji.hyperneat.nd.NDActivatorArray;
import com.anji.hyperneat.nd.NDActivatorArray.ActivatorArrayIterator;
import com.anji.hyperneat.onlinereinforcement.ActivatorNDABCHebbian;
import com.anji.hyperneat.onlinereinforcement.ActivatorNDBackPropagator;
import com.anji.hyperneat.onlinereinforcement.ActivatorNDHebbian;
import com.anji.hyperneat.onlinereinforcement.ActivatorNDTemporalDifference;
import com.anji.hyperneat.onlinereinforcement.GridNetHebbianABC;
import com.anji.hyperneat.onlinereinforcement.GridNetNDLR;
import com.anji.hyperneat.onlinereinforcement.GridNetTD;
import com.anji.hyperneat.onlinereinforcement.OnlineLearningNetType;
import com.anji.hyperneat.onlinereinforcement.RotationalBackPropActivator3d;
import com.anji.util.Properties;

public class Util {
	public static int getMaxIndex(double[] output) {
		int idx = 0;
		for (int i = 1; i < output.length; i++) {
			if (output[i] > output[idx]) 
				idx = i;
		}
		return idx;
	}
	
	public static Properties loadConfig(String configFileName) {
		Properties config = null;
		BufferedReader in = null;
		try {
			//is = Environment.class.getClassLoader().getResourceAsStream(configFileName);
			in = new BufferedReader(new FileReader(configFileName));
			config = new Properties();
			config.load(in);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try{
				if (null != in) in.close();
			} catch (IOException e) {
				e.printStackTrace();	
			}
		}	
		return config;
	}
	
	public static String timeStamp() {
//		Calendar calendar = Calendar.getInstance();
//		StringBuilder sb = new StringBuilder();
//		sb.append(calendar.get(Calendar.YEAR));
//		sb.append(".");
//		sb.append(calendar.get(Calendar.MONTH) < 9 ? "0" + (calendar.get(Calendar.MONTH)+1) : calendar.get(Calendar.MONTH)+1);
//		sb.append(".");
//		sb.append(calendar.get(Calendar.DAY_OF_MONTH) < 9 ? "0" + (calendar.get(Calendar.MONTH)+1) : calendar.get(Calendar.MONTH)+1);
//		sb.append("_");
//		sb.append(calendar.get(Calendar.HOUR_OF_DAY));
//		sb.append(".");
//		sb.append(calendar.get(Calendar.MINUTE));
//		sb.append(".");
//		sb.append(calendar.get(Calendar.SECOND));
//		return sb.toString();
		SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd.Hmmss");
		java.util.Date today = new java.util.Date();
		return String.valueOf(formatter.format(today.getTime())); 
	}
	
	public static double average(ArrayList<Double> errList) {
		double result = 0;
		for (Double err : errList) {
			result += err;
		}
		return result / errList.size();
	}
	public static double averageInts(ArrayList<Integer> list) {
		double result = 0;
		for (Integer i : list) {
			result += i;
		}
		return result / list.size();
	}

	public static double average(double[] vals) {
		double val = 0;
		for (int i = 0; i < vals.length; i++) val += vals[i];
		return val / vals.length;
	}
	public static float averageResults(ArrayList<Float> fitnessArray) {
		double result = 0;
		for (Float err : fitnessArray) {
			result += err;
		}
		return (float) (result / fitnessArray.size());
	}
	public static float average(float[] a) {
		double result = 0;
		for (float b : a) {
			result += b;
		}
		return (float) (result / a.length);
	}
	
	public static double calculateStdDev(List<Float> values, double average) {
		double[] stdVals = new double[values.size()];
		for (int i = 0; i < stdVals.length; i++) {
			stdVals[i] = Math.pow(values.get(i) - average, 2); 
		}
		
		return Math.sqrt(Util.average(stdVals));
	}
	
	public static double calculateStdDev(double[] values, double average) {
		double[] stdVals = new double[values.length];
		for (int i = 0; i < stdVals.length; i++) {
			stdVals[i] = Math.pow(values[i] - average, 2); 
		}
		
		return Math.sqrt(Util.average(stdVals));
	}
	
	public static boolean serializeChromosome(String fileName, Chromosome genoType) {
		boolean saveOk = false;
		ObjectOutputStream s = null;
		FileOutputStream out = null;
		try {
			if (fileName != null)
			//System.out.println("Saving Best Chromosome to " + fileName);
			out = new FileOutputStream(fileName);
			s = new ObjectOutputStream(out);
			s.writeObject(genoType);
			s.flush();
			saveOk = true;
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				if (s != null) {
					s.close();
				}
				
				if (out != null) {
					out.close();
				}
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
		
		//System.out.println("Saving Best Chromosome...Done");
		return (saveOk);
	}
	
	/**
	 * Retrieves a Layout descriptor from an Xml with the given file name.
	 * @param resource The name of the Xml file.
	 * @return The Layout descriptor.
	 * @throws JAXBException
	 */
	public static Layout getLayoutFromFile(String resource) throws JAXBException {
		InputStream inputStream = ClassLoader.getSystemResourceAsStream( resource );
	    String packageName = Layout.class.getPackage().getName();
	    JAXBContext jc = JAXBContext.newInstance( packageName );
	    Unmarshaller u = jc.createUnmarshaller();
//	    JAXBElement<Layout> doc = (JAXBElement<Layout>) u.unmarshal( inputStream );
	    Layout layout = (Layout) u.unmarshal( inputStream );
//	    return doc.getValue();
	    return layout;
	}
	
	public static boolean writeResultSetToFile(String filepath, float[] results, long[] chromosomeIds, int generationSize, String resultName) {
		boolean success = false;
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(filepath));
			bw.write("Generation,EvalCount,ChromosomeId,"+resultName + "\n");
			for (int i = 0; i < results.length; i++) {
				bw.write(((int)i/generationSize) + "," + i + "," + chromosomeIds[i] + "," + results[i] + "\n");
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != bw)
				try {
					bw.close();
					success = true;
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		
		return success;
	}
	
	public static boolean writeAggregateResultsToFile(String filepath, float[] results, int generationSize, String resultName) {
		boolean success = false;
		BufferedWriter bw = null;
		
		float[] averages = new float[results.length/generationSize];
		float[] mins = new float[averages.length];
		float[] maxs = new float[averages.length];
		
		for (int i = 0; i < averages.length; i++) averages[i] = 0.0f;
		for (int i = 0; i < mins.length; i++) mins[i] = Float.MAX_VALUE;
		for (int i = 0; i < maxs.length; i++) maxs[i] = 0.0f;
		for (int i = 0; i < results.length; i++) {
			averages[i/generationSize] += results[i] / (float)generationSize;
			if (results[i] > maxs[i/generationSize]) maxs[i/generationSize] = results[i];
			if (results[i] < mins[i/generationSize]) mins[i/generationSize] = results[i];
		}
		
		try {
			bw = new BufferedWriter(new FileWriter(filepath));
			bw.write("Generation,Average "+resultName+",Best "+resultName + ",Worst " + resultName + "\n");
			
			for (int i = 0; i < averages.length; i++) {
				bw.write( i + "," + averages[i] + "," + maxs[i] + "," + mins[i] +"\n");
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != bw)
				try {
					bw.close();
					success = true;
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		
		return success;
	}
	
	
	public static boolean writeAllAggregateResultsToFile(
			String filepath
			, int generationSize
			, String[] setsNames
			, float[][] setsResults

			) {
		boolean success = false;
		BufferedWriter bw = null;
		
		float[][] averages = new float[setsNames.length][setsResults[0].length/generationSize];
		float[][]  mins = new float[setsNames.length][averages[0].length];
		float[][]  maxs = new float[setsNames.length][averages[0].length];
		
		for (int i = 0; i < averages.length; i++) {
			for (int j = 0; j < averages[i].length; j++) {
				averages[i][j] = 0.0f;	
				maxs[i][j] = 0.0f;	
				mins[i][j] = Float.MAX_VALUE;	
			}
		}
		
		
		for (int set = 0; set < setsNames.length; set++) {
			for (int i = 0; i < setsResults[set].length; i++) {
				averages[set][i/generationSize] += setsResults[set][i] / (float)generationSize;
				if (setsResults[set][i] > maxs[set][i/generationSize]) maxs[set][i/generationSize] = setsResults[set][i];
				if (setsResults[set][i] < mins[set][i/generationSize]) mins[set][i/generationSize] = setsResults[set][i];
			}
		}
		
		try {
			bw = new BufferedWriter(new FileWriter(filepath));
			bw.write("Generation");
			for (int set = 0; set < setsNames.length; set++) {
				bw.write(",Average "+setsNames[set]+",Best "+setsNames[set] + ",Worst " + setsNames[set]);
			}
			
			bw.write("\n");
			
			
			for (int i = 0; i < averages[0].length; i++) {
				bw.write( ""+i );
				for (int set = 0; set < setsNames.length; set++) {
					bw.write( "," + averages[set][i] + "," + maxs[set][i] + "," + mins[set][i]);
				}
				bw.write("\n");
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != bw)
				try {
					bw.close();
					success = true;
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		
		return success;
	}
	
	public static NDActivatorArray buildLearningNets(NDActivatorArray substrate, OnlineLearningNetType learningType) {
		NDActivatorArray nets = new NDActivatorArray(substrate.getDimensions());
		for (ActivatorArrayIterator lrnet = nets.iterator(), net = substrate.iterator()
				; lrnet.hasNext()
				; lrnet.next(), net.next()
			) {
			switch(learningType) {
			case BACKPROPAGATION:
				lrnet.set(new ActivatorNDBackPropagator((GridNetNDLR)net.get()));
				break;
			case HEBBIAN:
				lrnet.set(new ActivatorNDHebbian((GridNetNDLR)net.get()));
				break;
			case HEBBIANABC:
				lrnet.set(new ActivatorNDABCHebbian((GridNetHebbianABC)net.get()));
				break;
			case TEMPORALDIFFERENCE:
				lrnet.set(new ActivatorNDTemporalDifference((GridNetTD)net.get()));
				break;
			case ROTATIONALBP3D:
				lrnet.set(new RotationalBackPropActivator3d((GridNetNDLR)net.get()));
				break;
			case MULTIBP:
				lrnet.set(new ActivatorNDMultiBackProp((GridNetNDLR)net.get()));
				break;
			}
		}
		
		return nets;
	}
}
